package com.swak.fx.support.ability;

import java.util.function.Function;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.util.Duration;

/**
 * 设置 丝滑滚动
 * 
 * @author 618lf
 */
public class SmoothScrollAbility {
	
	final ScrollPane scrollPane;
	final double[] frictions = { 0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008,
			0.0006, 0.0005, 0.00003, 0.00001 };
	final double[] pushes = { 1 };
	final double[] derivatives = new double[frictions.length];

	final Timeline timeline = new Timeline();
	final EventHandler<MouseEvent> dragHandler = event -> timeline.stop();
	final EventHandler<ScrollEvent> scrollHandler = event -> {
		if (event.getEventType() == ScrollEvent.SCROLL) {
			int direction = event.getDeltaY() > 0 ? -1 : 1;
			for (int i = 0; i < pushes.length; i++) {
				derivatives[i] += direction * pushes[i];
			}
			if (timeline.getStatus() == Animation.Status.STOPPED) {
				timeline.play();
			}
			event.consume();
		}
	};

	public SmoothScrollAbility(ScrollPane scrollPane, DoubleProperty scrollDriection,
			Function<Bounds, Double> sizeFunc) {
		this.scrollPane = scrollPane;
		this.timeline.getKeyFrames().add(new KeyFrame(Duration.millis(3), (event) -> {
			for (int i = 0; i < derivatives.length; i++) {
				derivatives[i] *= frictions[i];
			}
			for (int i = 1; i < derivatives.length; i++) {
				derivatives[i] += derivatives[i - 1];
			}
			double dy = derivatives[derivatives.length - 1];
			double size = sizeFunc.apply(scrollPane.getContent().getLayoutBounds());
			scrollDriection.set(Math.min(Math.max(scrollDriection.get() + dy / size, 0), 1));
			if (Math.abs(dy) < 0.001) {
				timeline.stop();
			}
		}));
		this.timeline.setCycleCount(Animation.INDEFINITE);
		this.scrollPane.getContent().parentProperty().addListener((o, oldVal, newVal) -> {
			if (oldVal != null) {
				oldVal.removeEventHandler(MouseEvent.DRAG_DETECTED, dragHandler);
				oldVal.removeEventHandler(ScrollEvent.ANY, scrollHandler);
			}
			if (newVal != null) {
				newVal.addEventHandler(MouseEvent.DRAG_DETECTED, dragHandler);
				newVal.addEventHandler(ScrollEvent.ANY, scrollHandler);
			}
		});
		this.enableSmoothScroll();
	}

	public SmoothScrollAbility enableSmoothScroll() {
		if (this.scrollPane.getContent().getParent() != null) {
			scrollPane.getContent().getParent().addEventHandler(MouseEvent.DRAG_DETECTED, dragHandler);
			scrollPane.getContent().getParent().addEventHandler(ScrollEvent.ANY, scrollHandler);
		}
		return this;
	}
	
	public SmoothScrollAbility disableSmoothScroll() {
		if (this.scrollPane.getContent().getParent() != null) {
			this.scrollPane.getContent().getParent().removeEventHandler(MouseEvent.DRAG_DETECTED, dragHandler);
			this.scrollPane.getContent().getParent().removeEventHandler(ScrollEvent.ANY, scrollHandler);
		}
	    return this;
	}

	public static SmoothScrollAbility vof(ScrollPane scrollPane) {
		return new SmoothScrollAbility(scrollPane, scrollPane.vvalueProperty(), bounds -> bounds.getHeight());
	}

	public static SmoothScrollAbility hof(ScrollPane scrollPane) {
		return new SmoothScrollAbility(scrollPane, scrollPane.hvalueProperty(), bounds -> bounds.getWidth());
	}
}